package cn.yunsenlin.formula.cloud.result;

import cn.yunsenlin.formula.cloud.bean.auth.Template;
import cn.yunsenlin.formula.cloud.bean.result.Result;
import cn.yunsenlin.formula.cloud.bean.result.ResultRequest;
import cn.yunsenlin.formula.cloud.bean.template.TemplateTest;
import cn.yunsenlin.formula.cloud.interfaces.GroupService;
import cn.yunsenlin.formula.cloud.interfaces.ResultService;
import cn.yunsenlin.formula.cloud.interfaces.TemplateService;
import cn.yunsenlin.formula.cloud.mysql.bean.generator.*;
import cn.yunsenlin.formula.cloud.mysql.mapper.generator.TbColumnMapper;
import cn.yunsenlin.formula.cloud.mysql.mapper.generator.TbResultContentMapper;
import cn.yunsenlin.formula.cloud.mysql.mapper.generator.TbResultMapper;
import com.googlecode.aviator.AviatorEvaluator;
import com.googlecode.aviator.Expression;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;

@Transactional
@Service
public class ResultServiceImpl implements ResultService {
    private final TbResultMapper tbResultMapper;
    private final TbResultContentMapper tbResultContentMapper;
    private final TbColumnMapper tbColumnMapper;
    private final TemplateService templateService;
    private final GroupService groupService;
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @SuppressWarnings("all")
    public ResultServiceImpl(TbResultMapper tbResultMapper, TbResultContentMapper tbResultContentMapper, TbColumnMapper tbColumnMapper, TemplateService templateService, GroupService groupService) {
        this.tbResultMapper = tbResultMapper;
        this.tbResultContentMapper = tbResultContentMapper;
        this.tbColumnMapper = tbColumnMapper;
        this.templateService = templateService;
        this.groupService = groupService;
    }

    @Override
    public Result analyze(ResultRequest request) {
        return analyze(request, new Date(), new Date());
    }

    @Override
    public void remove(int resultId) {
        TbResultContentCriteria criteria = new TbResultContentCriteria();
        criteria.or().andResultIdEqualTo(resultId);
        tbResultContentMapper.deleteByExample(criteria);
        tbResultMapper.deleteByPrimaryKey(resultId);
    }

    @Override
    public void removeByGroupId(int groupId) {
        groupService.selectSonGroup(groupId).forEach(tbGroup -> removeByGroupId(tbGroup.getId()));
        TbResultCriteria criteria = new TbResultCriteria();
        criteria.or().andGroupIdEqualTo(groupId);
        tbResultMapper.selectByExample(criteria).forEach(tbResult -> remove(tbResult.getId()));
    }

    @Override
    public Result selectById(int resultId) {
        Result result = new Result();
        result.setBaseInfo(tbResultMapper.selectByPrimaryKey(resultId));
        result.setInput(selectInputByResultId(resultId));
        result.setOutput(selectOutputByResultId(resultId));
        return result;
    }

    @Override
    public List<Result> selectByGroupIdAndTemplateId(int groupId, int templateId) {
        List<Result> resultList = new ArrayList<>();
        TbResultCriteria criteria = new TbResultCriteria();
        criteria.or().andGroupIdEqualTo(groupId).andTemplateIdEqualTo(templateId);
        tbResultMapper.selectByExample(criteria).forEach(tbResult -> resultList.add(selectById(tbResult.getId())));
        return resultList;
    }

    @Override
    public Result modify(ResultRequest request) {
        Result result = selectById(request.getId());
        remove(request.getId());
        return analyze(request, result.getBaseInfo().getCreateTime(), new Date());
    }

    @Override
    public Result moveGroup(int resultId, int groupId) {
        TbResult tbResult = tbResultMapper.selectByPrimaryKey(resultId);
        tbResult.setGroupId(groupId);
        tbResultMapper.updateByPrimaryKey(tbResult);
        return selectById(resultId);
    }

    @Override
    public Result templateTest(TemplateTest templateTest) {
        Template template = templateService.createTempTemplate(templateTest.getTemplate());
        ResultRequest request = new ResultRequest();
        templateTest.getResultContents().forEach(tbResultContent -> template.getInput().forEach(tbColumn -> {
            if (tbColumn.getSymbol().equals(tbResultContent.getSymbol())) {
                tbResultContent.setColumnId(tbColumn.getId());
            }
        }));
        request.setGroupId(0);
        List<TbResultContent> contents = new ArrayList<>(templateTest.getResultContents());
        request.setResultContentList(contents);
        request.setTemplateId(template.getBaseInfo().getId());
        Result r = analyze(request);
        remove(r.getBaseInfo().getId());
        templateService.removeTemplate(template.getBaseInfo().getId());
        return r;
    }

    @Override
    public String checkValidWithTemplateId(List<TbResultContent> tbResultContentList, int templateId, String symbol) {
        Map<String, Object> env = getInputVariables(tbResultContentList);
        return checkValidSymbol(env, templateId, symbol);
    }

    @Override
    public String checkValidByTemplateTest(TemplateTest templateTest, String symbol, int userId) {
        Template template = templateService.createTemplate(templateTest.getTemplate(), userId);
        templateTest.getResultContents().forEach(resultContent -> template.getInput().forEach(tbColumn -> {
            if (resultContent.getSymbol().equals(tbColumn.getSymbol())) {
                resultContent.setColumnId(tbColumn.getId());
            }
        }));
        Map<String, Object> env = getInputVariables(templateTest.getResultContents());
        String r = checkValidSymbol(env, template.getBaseInfo().getId(), symbol);
        templateService.removeTemplate(template.getBaseInfo().getId());
        return r;
    }

    /**
     * 检查当前输入项的输入
     *
     * @param env        输入环境
     * @param templateId 公式ID
     * @param symbol     输入项符号
     * @return 返回error：计算错误，valid：合法，unValid：不合法
     */
    private String checkValidSymbol(Map<String, Object> env, int templateId, String symbol) {
        Template template = templateService.selectById(templateId);
        String r = "valid";
        for (TbColumn tbColumn : template.getInput()) {
            if (tbColumn.getValidatorFormula() != null &&
                    tbColumn.getValidatorFormula().length() > 0 &&
                    tbColumn.getSymbol().equals(symbol)) {
                Expression compiledExp = AviatorEvaluator.compile(tbColumn.getValidatorFormula());
                try {
                    Object o = compiledExp.execute(env);
                    if (o instanceof Boolean) {
                        if (!(Boolean) o) {
                            r = "unValid";
                        }
                    } else {
                        r = "error";
                    }
                } catch (Exception e) {
                    r = "error";
                }
            }
        }
        return r;
    }

    /**
     * 计算并储存
     *
     * @param request    计算请求
     * @param createTime 创建时间
     * @param modifyTime 删除时间
     * @return 计算结果
     */
    private Result analyze(ResultRequest request, Date createTime, Date modifyTime) {
        TbResult result = new TbResult();
        result.setCreateTime(createTime);
        result.setModifyTime(modifyTime);
        result.setGroupId(request.getGroupId());
        result.setTemplateId(request.getTemplateId());
        tbResultMapper.insert(result);
        Result r = new Result();
        r.setBaseInfo(result);
        r.setInput(setResultInputContent(request.getResultContentList(), result.getId()));
        r.setOutput(generateOutPut(getInputVariables(r.getInput()), result.getId()));
        saveResultContent(r.getInput());
        saveResultContent(r.getOutput());
        return r;
    }

    /**
     * 设置输入列表
     *
     * @param tbResultContents 输入列表
     * @param resultId         计算结果ID
     * @return 输入列表
     */
    private List<TbResultContent> setResultInputContent(List<TbResultContent> tbResultContents, int resultId) {
        tbResultContents.forEach(tbResultContent -> {
            tbResultContent.setResultId(resultId);
            tbResultContent.setSymbol(tbColumnMapper.selectByPrimaryKey(tbResultContent.getColumnId()).getSymbol());
            tbResultContent.setType("input");
        });
        return tbResultContents;
    }

    /**
     * 存储输入/输出列表
     *
     * @param tbResultContents 输入/输出列表
     */
    private void saveResultContent(List<TbResultContent> tbResultContents) {
        tbResultContents.forEach(tbResultContentMapper::insert);
    }

    /**
     * 查询输入环境
     *
     * @param tbResultContents 输入列表
     * @return 输入环境
     */
    private Map<String, Object> getInputVariables(List<TbResultContent> tbResultContents) {
        Map<String, Object> env = new HashMap<>();
        tbResultContents.forEach(tbResultContent -> {
            TbColumn tbColumn = tbColumnMapper.selectByPrimaryKey(tbResultContent.getColumnId());
            switch (tbColumn.getDataType()) {
                case "number":
                    env.put(tbColumn.getSymbol(), tbResultContent.getNumberContent());
                    tbResultContent.setContent(tbResultContent.getNumberContent().toString());
                    break;
                case "string":
                    env.put(tbColumn.getSymbol(), tbResultContent.getStringContent());
                    tbResultContent.setContent(tbResultContent.getStringContent());
                    break;
                case "boolean":
                    env.put(tbColumn.getSymbol(), tbResultContent.getBooleanContent());
                    tbResultContent.setContent(tbResultContent.getBooleanContent().toString());
                    break;
                case "date":
                case "time":
                case "datetime":
                    env.put(tbColumn.getSymbol(), tbResultContent.getDateContent());
                    tbResultContent.setContent(tbResultContent.getDateContent().toString());
                    break;
            }
        });
        return env;
    }

    /**
     * 生成输出列表
     *
     * @param env      输入环境
     * @param resultId 结果ID
     * @return 输出列表
     */
    private List<TbResultContent> generateOutPut(Map<String, Object> env, int resultId) {
        List<TbResultContent> list = new ArrayList<>();
        Template template = templateService.selectById(tbResultMapper.selectByPrimaryKey(resultId).getTemplateId());
        template.getOutput().forEach(column -> {
            TbResultContent content = new TbResultContent();
            Expression compiledExp = AviatorEvaluator.compile(column.getFormula());
            content.setType("output");
            content.setResultId(resultId);
            content.setColumnId(column.getId());
            content.setSymbol(column.getSymbol());
            try {
                switch (column.getDataType()) {
                    case "number":
                        Object result = compiledExp.execute(env);
                        BigDecimal bigDecimal;
                        if (result instanceof Double) {
                            bigDecimal = new BigDecimal((Double) result);
                        } else if (result instanceof Long) {
                            bigDecimal = new BigDecimal((Long) result);
                        } else if (result instanceof BigInteger) {
                            bigDecimal = new BigDecimal((BigInteger) result);
                        } else {
                            bigDecimal = (BigDecimal) result;
                        }
                        content.setNumberContent(bigDecimal);
                        content.setContent(bigDecimal.toString());
                        env.put(column.getSymbol(), bigDecimal);
                        break;
                    case "string":
                        String string = (String) compiledExp.execute(env);
                        content.setStringContent(string);
                        content.setContent(string);
                        env.put(column.getSymbol(), string);
                        break;
                    case "boolean":
                        Boolean bool = (Boolean) compiledExp.execute(env);
                        content.setBooleanContent(bool);
                        content.setContent(bool.toString());
                        env.put(column.getSymbol(), bool);
                        break;
                    case "date":
                    case "time":
                    case "datetime":
                        Date date = (Date) compiledExp.execute(env);
                        content.setDateContent(date);
                        content.setContent(date.toString());
                        env.put(column.getSymbol(), date);
                        break;
                }
                content.setSuccess(true);
            } catch (Exception e) {
                content.setContent("计算错误");
                content.setSuccess(false);
                logger.error(e.getMessage(), e);
            }
            list.add(content);
        });
        return list;
    }

    /**
     * 查询输入列表
     *
     * @param resultId 计算结果ID
     * @return 输入列表
     */
    private List<TbResultContent> selectInputByResultId(int resultId) {
        TbResultContentCriteria input = new TbResultContentCriteria();
        input.or().andResultIdEqualTo(resultId).andTypeEqualTo("input");
        return tbResultContentMapper.selectByExample(input);
    }

    /**
     * 查询输出列表
     *
     * @param resultId 计算结果ID
     * @return 输出列表
     */
    private List<TbResultContent> selectOutputByResultId(int resultId) {
        TbResultContentCriteria output = new TbResultContentCriteria();
        output.or().andResultIdEqualTo(resultId).andTypeEqualTo("output");
        return tbResultContentMapper.selectByExample(output);
    }
}
